package com.basswold.nasaesa.client;

import java.util.ArrayList;

import com.basswold.nasaesa.client.ConstructionModule.ModuleType;

/**
 * Abstract parent class for base building operations.
 * @author Keith
 *
 */
public abstract class BaseConstructor {
	
	/**
	 * The list of modules that a base constructor can use to build a base.
	 */
	protected ContructionList modules;
	
	final static int arrayDimension = 50;
	final static int arrayMidpoint = arrayDimension/2;
	
	/**
	 * Constructor.
	 * @param array The array of integers representing the IDs of available
	 * 		modules that can be used in base construction.
	 */
	public BaseConstructor(final ModuleList modList) {
		modules = new ContructionList(modList);
	}
	
	/**
	 * Abstract class to fully construct a base from the available modules.
	 * @return A 2D array of modules representing the completed base.
	 */
	public abstract Module[][] BuildBase();
	
	/**
	 * Gets the wing factories that will be used by a particular base
	 * constructor to build a base.
	 * @return An array list of building wing factories, each one representing
	 * 		a wing that will be constructed independently and added to the 
	 * 		base.
	 */
	public abstract ArrayList<BuildingWingFactory> getWingFactories();
	
	/**
	 * Builds a series of separate wings in parallel.
	 * @param wingFactories A list of BuildingWingFactories that will construct
	 * 		the separate building wings needed for the base.
	 * @precondition the factories provided by getWingFactories have each
	 * 		already been initialized.
	 */
	protected void buildWings() {
		
		ArrayList<BuildingWingFactory >wingFactories = getWingFactories();
		assert wingFactories != null;
		assert wingFactories.size() > 0;

		ArrayList<BuildingWingFactory> completedWings =
			new ArrayList<BuildingWingFactory>();
		
		// Repeatedly add modules to each consecutive building wing, in order,
		// until no wing can be add any more modules.
		while (wingFactories.size() > 0) {
			
			// Loop through unfinished building wings and add modules.
			int i = 0;
			while (i < wingFactories.size()) {
				boolean success = wingFactories.get(i).addNextModule();
				if (!success) {
					completedWings.add(wingFactories.get(i));
					wingFactories.remove(i);
				} else {
					i++;
				}
			}
		}
	}
	
	/**
	 * Convert the 2D array of construction modules to a 2D array of normal
	 * modules for use with the UI.
	 * @param modules The 2D array of construction modules to be converted
	 * 		into normal modules.
	 */
	public static Module[][] convertToOutputModules(ConstructionModule[][] modules) {
		// Get width of 2d array
		int cols = modules[0].length;
		
		// loop through rows of the array until we get an exception, this 
		// provides us with height of the 2D array.
		int rows = 0;
		try {
			while (true) {
				ConstructionModule temp = modules[rows][0];
				rows++;
			} 
		} catch (ArrayIndexOutOfBoundsException ex) {
		}
		
		// Array to hold normal modules
		Module[][] mods = new Module[rows][cols];
		
		// Loop through array of construction modules, copy data into normal 
		// modules and and store in array of normal modules.
		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < cols; j++) {
				if (modules[i][j] != null) {
					ConstructionModule tempMod = modules[i][j];
					Module newModule = new Module(i, j, tempMod.ID, tempMod.condition, tempMod.orientation);
					mods[i][j] = newModule;
				}
			}
		}
		
		return mods;
	}
	
	/**
	 * Given a graph of modules convert that into a 2D array of modules.
	 * @param module The middle module of the base from which conversion
	 * 		into an array should start from.
	 * @return An excessively large 2D array of modules.
	 */
	public static ConstructionModule[][] convertToArray(ConstructionModule module) {
		
		// Create an array large enough to hold the base.
		ConstructionModule[][] moduleArray = new ConstructionModule[arrayDimension][arrayDimension];
		
		traverseBase(module, arrayMidpoint, arrayMidpoint, moduleArray);
		
		return moduleArray;
	}
	
	/**
	 * Recursively traverse a base structure and place it within an overly
	 * large 2D module array. There will be no chance of infinite recursion
	 * because there are no loops in the graph representing the base. 
	 * @param module The module that is being visited for the first time.
	 * @param row The row in the moduleArray that this module should be 
	 * 		stored into.
	 * @param col The column in the moduleArray that this module should be 
	 * 		stored into.
	 * @param moduleArray The 2D array of modules that the base is being copied
	 * 		into.
	 */
	private static void traverseBase(ConstructionModule module, int row, int col,
			ConstructionModule[][] moduleArray) {
		moduleArray[row][col] = module;
		
		if (module.getType() == ModuleType.PLAIN) {
			// recursively check other modules connected to this
			ConstructionConnectorModule connector = (ConstructionConnectorModule)module;
			if (connector.hasNorthConnection() 
					&& moduleArray[row - 1][col] == null) {
				traverseBase(connector.getNorthModule(), row - 1, col, moduleArray);
			}
			if (connector.hasEastConnection() 
					&& moduleArray[row][col + 1] == null) {
				traverseBase(connector.getEastModule(), row, col + 1, moduleArray);
			}
			if (connector.hasSouthConnection() 
					&& moduleArray[row + 1][col] == null) {
				traverseBase(connector.getSouthModule(), row + 1, col, moduleArray);
			}
			if (connector.hasWestConnection() 
					&& moduleArray[row][col - 1] == null) {
				traverseBase(connector.getWestModule(), row, col - 1, moduleArray);
			}
			
		}
	}
	
	/**
	 * Convert an overly large 2D array that contains rows and columns that
	 * 		contain no modules themselves into a smaller 2D array where every
	 * 		row and column has at least one module.
	 * @param moduleArray The untrimmed moduleArray. This is an array with
	 * 		rows or columns in the array that contain no modules. This 
	 * 		array is always squarae.
	 * @return A 2D array of modules where every row and column has at least
	 * 		one module.
	 */
	public static ConstructionModule[][] trimArray(ConstructionModule[][] moduleArray) {

		int cols = moduleArray[0].length;
		int rows = cols;
		
		// get row number of first now that a module is located in.
		int minRow = 0;
		boolean found = false;
		for (int i = 0; i < rows && !found; i++) {
			for (int j = 0; j < cols && !found; j++) {
				if (moduleArray[i][j] != null) {
					// We have found a module.
					minRow = i;
					found = true;
				}
			}
		}
		
		// get row number of last row that a module is located in.
		int maxRow = 0;
		for (int i = minRow; i < rows; i++) {
			boolean moduleFound = false;
			for (int j = 0; j < cols; j++) {
				if (moduleArray[i][j] != null) {
					// We have found a module.
					moduleFound = true;
				}
			}
			
			// Finished checking all columns in this row.
			if (moduleFound == false) {
				// There were no modules in this row
				maxRow = i - 1;
				break;
			}
		}
		
		// get column number of first now that a module is located in.
		int minCol = 0;
		found = false;
		for (int i = 0; i < rows && !found; i++) {
			for (int j = 0; j < cols && !found; j++) {
				if (moduleArray[j][i] != null) {
					// We have found a module.
					minCol = i;
					found = true;
				}
			}
		}
		
		// get row number of last row that a module is located in.
		int maxCol = 0;
		for (int i = minCol; i < rows; i++) {
			boolean moduleFound = false;
			for (int j = 0; j < cols; j++) {
				if (moduleArray[j][i] != null) {
					// We have found a module.
					moduleFound = true;
				}
			}
			
			// Finished checking all columns in this row.
			if (moduleFound == false) {
				// There were no modules in this row
				maxCol = i - 1;
				break;
			}
		}
		
		// now make a new array of the size specified
		ConstructionModule[][] trimmedArray = 
			new ConstructionModule[(maxRow - minRow) + 1][(maxCol - minCol) + 1];
		
		// Populate the trimmed array with items from the untrimmed array.
		for (int i = minRow; i <= maxRow; i++) {
			for (int j = minCol; j <= maxCol; j++) {
				trimmedArray[i - minRow][j - minCol] = 
					moduleArray[i][j];
			}
		}
		
		return trimmedArray;
	}
	
	/**
	 * Convert a 2D array base structure into a textual representation.
	 * @param base A 2D array of modules representing a completed base.
	 * @param rows The number of rows in the 2D array.
	 * @param cols The number of columns in the 2D array.
	 * @return The string representation of the base.
	 */
	public static String DisplayBase(Module[][] base) {
		assert base != null;
		
		// Get width of 2d array
		int cols = base[0].length;
		
		// loop through rows of the array until we get an exception, this 
		// provides us with height of the 2D array.
		int rows = 0;
		try {
			while (true) {
				Module temp = base[rows][0];
				rows++;
			} 
		} catch (ArrayIndexOutOfBoundsException ex) {
		}
		
		String output = "";
		for (int i = 0; i < rows; i++) {
			for (int j = 0; j < cols; j++) {
				if (base[i][j] == null) {
					output += ("[   ]");
				} else {

					if (base[i][j].getType() == "Plain") {
						output += ("[Pln]");
					} else if (base[i][j].getType() == "Dormitory") {
						output += ("[Drm]");
					} else if (base[i][j].getType() == "Sanitation") {
						output += ("[San]");
					} else if (base[i][j].getType() == "Food") {
						output += ("[FWS]");
					} else if (base[i][j].getType() == "Gym") {
						output += ("[Gym]");
					} else if (base[i][j].getType() == "Canteen") {
						output += ("[Ctn]");
					} else if (base[i][j].getType() == "Power") {
						output += ("[Pwr]");
					} else if (base[i][j].getType() == "Control") {
						output += ("[Con]");
					} else if (base[i][j].getType() == "Airlock") {
						output += ("[Air]");
					} else if (base[i][j].getType() == "Medical") {
						output += ("[Med]");
					} else {
						output += ("[   ]");
					}
				}
			}
			output += "\r\n";
		}
		
		return output;
	}
}
